from app.crud.file_crud import check_file_unbound, update_file_record, get_file_record_with_db
from app.schemas.file_schemas import FileUpdateRequest
from app.service.file_storage import file_storage
from common.database import DatabaseService
from common.exceptions import BizException, ErrorCode
from common.logger import create_logger

logger = create_logger(__name__)


class FileService:
	"""文件服务类，处理文件相关的业务逻辑"""

	@staticmethod
	async def bind_file_to_business(
			db: DatabaseService, file_id: int, biz_type: str, biz_id: str
	) -> bool:
		"""
		将文件绑定到指定业务

		Args:
			db: 数据库服务
			file_id: 文件ID
			biz_type: 业务类型
			biz_id: 业务ID

		Returns:
			bool: 绑定成功返回True，文件不存在或已被绑定返回False

		Raises:
			BizException: 绑定过程中出现数据库错误时抛出
		"""
		try:
			# 1. 检查文件是否存在且未被绑定
			is_unbound = await check_file_unbound(db, file_id)

			if not is_unbound:
				# 文件不存在或已被绑定
				logger.info(f"文件绑定失败：文件不存在或已被绑定，file_id={file_id}")
				return False

			# 2. 执行绑定操作
			update_request = FileUpdateRequest(biz_type=biz_type, biz_id=biz_id)
			updated_file = await update_file_record(db, file_id, update_request)

			if updated_file:
				logger.info(
					f"文件绑定成功：file_id={file_id}, biz_type={biz_type}, biz_id={biz_id}"
				)
				return True
			else:
				logger.warning(f"文件绑定失败：更新文件记录失败，file_id={file_id}")
				return False

		except Exception as e:
			logger.exception(
				f"文件绑定异常：file_id={file_id}, biz_type={biz_type}, biz_id={biz_id}"
			)
			raise BizException.from_error_code(
				ErrorCode.DATABASE_ERROR, message="文件绑定失败") from e

	@staticmethod
	async def batch_bind_files_to_business(
			db: DatabaseService, file_ids: list, biz_type: str, biz_id: str
	) -> dict:
		"""
		批量将文件绑定到指定业务

		Args:
			db: 数据库服务
			file_ids: 文件ID列表
			biz_type: 业务类型
			biz_id: 业务ID

		Returns:
			dict: 包含成功和失败的文件ID信息
		"""
		success_files = []
		failed_files = []

		for file_id in file_ids:
			try:
				result = await FileService.bind_file_to_business(
					db, file_id, biz_type, biz_id
				)
				if result:
					success_files.append(file_id)
				else:
					failed_files.append(file_id)
			except Exception as e:
				logger.error(f"文件绑定异常：file_id={file_id}, error={str(e)}")
				failed_files.append(file_id)

		return {
			"success_count": len(success_files),
			"success_files": success_files,
			"failed_count": len(failed_files),
			"failed_files": failed_files,
		}

	@staticmethod
	async def unbind_file_from_business(
			db: DatabaseService, file_id: int
	) -> bool:
		"""
		将文件与业务解绑

		Args:
			db: 数据库服务
			file_id: 文件ID

		Returns:
			bool: 解绑成功返回True，文件不存在返回False

		Raises:
			BizException: 解绑过程中出现数据库错误时抛出
		"""
		try:
			# 1. 执行解绑操作 - 将biz_type和biz_id设为None
			update_request = FileUpdateRequest(biz_type=None, biz_id=None)
			updated_file = await update_file_record(db, file_id, update_request)

			if updated_file:
				logger.info(f"文件解绑成功：file_id={file_id}")
				return True
			else:
				logger.warning(f"文件解绑失败：文件不存在或更新文件记录失败，file_id={file_id}")
				return False

		except Exception as e:
			logger.exception(f"文件解绑异常：file_id={file_id}")
			raise BizException.from_error_code(
				ErrorCode.DATABASE_ERROR, message="文件解绑失败") from e

	@staticmethod
	async def batch_unbind_files_from_business(
			db: DatabaseService, file_ids: list
	) -> dict:
		"""
		批量将文件与业务解绑

		Args:
			db: 数据库服务
			file_ids: 文件ID列表

		Returns:
			dict: 包含成功和失败的文件ID信息
		"""
		success_files = []
		failed_files = []

		for file_id in file_ids:
			try:
				result = await FileService.unbind_file_from_business(db, file_id)
				if result:
					success_files.append(file_id)
				else:
					failed_files.append(file_id)
			except Exception as e:
				logger.error(f"文件解绑异常：file_id={file_id}, error={str(e)}")
				failed_files.append(file_id)

		return {
			"success_count": len(success_files),
			"success_files": success_files,
			"failed_count": len(failed_files),
			"failed_files": failed_files,
		}

	@staticmethod
	async def generate_presigned_url(db: DatabaseService, file_id: int) -> str:
		"""
		生成文件的带签名URL，用于直接查看
		
		Args:
			db: 数据库服务
			file_id: 文件ID
			
		Returns:
			str: 带签名的URL
			
		Raises:
			BizException: 文件不存在时抛出
		"""
		# 将整数类型的file_id转换为字符串后再传给get_file_record_with_db
		file_record = await get_file_record_with_db(db, str(file_id))
		if not file_record:
			raise BizException.file_not_found(file_id)
			
		# 生成签名URL（公网域名，900 s）
		return file_storage.get_signed_url(file_record.store_path, expires=900, internal=False)
			
	@staticmethod
	async def update_files_for_business(
			db: DatabaseService,
			biz_type: str,
			biz_id: str,
			add_file_ids: list = None,
			del_file_ids: list = None,
			new_file_ids: list = None
	) -> dict:
		"""
		更新业务相关的文件，支持增量和全量模式

		Args:
			db: 数据库服务
			biz_type: 业务类型
			biz_id: 业务ID
			add_file_ids: 要添加的文件ID列表（增量模式）
			del_file_ids: 要删除的文件ID列表（增量模式）
			new_file_ids: 新的文件ID列表（全量模式），如果提供了此参数，则忽略add_file_ids和del_file_ids

		Returns:
			dict: 包含成功和失败的文件操作信息
		
		Raises:
			BizException: 操作过程中出现数据库错误时抛出
		"""
		try:
			# 开始事务处理
			async with db.transaction():
				result = {}

				if new_file_ids is not None:
					# 全量模式：先解绑所有当前业务的文件，再绑定新的文件
					from app.crud.file_crud import get_files_by_business
					existing_files = await get_files_by_business(db, biz_type, biz_id)
					existing_file_ids = [file.id for file in existing_files]

					# 解绑现有文件
					if existing_file_ids:
						unbind_result = await FileService.batch_unbind_files_from_business(db, existing_file_ids)
						result["unbind_existing"] = unbind_result

					# 绑定新文件
					if new_file_ids:
						bind_result = await FileService.batch_bind_files_to_business(db, new_file_ids, biz_type, biz_id)
						result["bind_new"] = bind_result
				else:
					# 增量模式：添加和删除指定文件
					# 解绑要删除的文件
					if del_file_ids:
						unbind_result = await FileService.batch_unbind_files_from_business(db, del_file_ids)
						result["unbind"] = unbind_result

					# 绑定要添加的文件
					if add_file_ids:
						bind_result = await FileService.batch_bind_files_to_business(db, add_file_ids, biz_type, biz_id)
						result["bind"] = bind_result

				logger.info(f"业务文件更新成功：biz_type={biz_type}, biz_id={biz_id}")
				return result
		except Exception as e:
			logger.exception(f"业务文件更新异常：biz_type={biz_type}, biz_id={biz_id}")
			raise BizException.from_error_code(
				ErrorCode.DATABASE_ERROR, message="更新业务文件失败") from e


# 全局文件服务实例
file_service = FileService()
